/*
  Copyright 2012 Sergey Ostanin

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

package ru.irenproject;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.lang3.Validate;

import ru.irenproject.TestProtos.SectionNode;
import ru.irenproject.UiProtos.EditableSectionNode;
import ru.irenproject.UiProtos.SectionTreeEditorShape;
import ru.irenproject.store.AbstractThing;
import ru.irenproject.store.Event;
import ru.irenproject.store.Ids;
import ru.irenproject.store.Stash;
import ru.irenproject.store.Thing;

import com.google.protobuf.ByteString;
import com.google.protobuf.Message;

public final class SectionTreeEditor extends AbstractThing implements Visual, SectionPathSupplier {
  public enum SelectSectionEvent implements Event { INSTANCE }

  public static final class AddInput {
    public String name;
  }

  public static final class SelectInput {
    public String id;
  }

  public static final class MoveInput {
    public boolean forward;
  }

  public static SectionTreeEditor create(SectionTree tree) {
    SectionTreeEditor res = tree.store().create(SectionTreeEditor.class);
    res.prepare(tree);
    return res;
  }

  @Stash private UiProtos.SectionTreeEditor.Builder $;

  @Inject SectionTreeEditor() {}

  private void prepare(SectionTree tree) {
    $.setSectionTree(tree.id());
    $.setSelection(store().create(SectionTreeSelection.class).id());
    syncEditors();
    selection().setSelectedEditor(rootEditor());
    sectionTree().addListener(this);
  }

  public SectionTree sectionTree() {
    return get($.getSectionTree(), SectionTree.class);
  }

  /**
   * @return SectionEditor ids
   */
  private Set<ByteString> syncEditors() {
    Set<ByteString> res = new HashSet<>();
    $.setRoot(sync(sectionTree().rootNode(), res));
    return res;
  }

  private EditableSectionNode sync(SectionNode source, Set<ByteString> editorIds) {
    EditableSectionNode.Builder b = EditableSectionNode.newBuilder();

    Section section = get(source.getSection(), Section.class);
    SectionEditor editor = SectionEditor.open(section);
    b.setEditor(editor.id());
    editorIds.add(editor.id());

    for (SectionNode child : source.getChildList()) {
      b.addChild(sync(child, editorIds));
    }

    return b.build();
  }

  private SectionTreeSelection selection() {
    return get($.getSelection(), SectionTreeSelection.class);
  }

  private SectionEditor rootEditor() {
    return get($.getRoot().getEditor(), SectionEditor.class);
  }

  @Override public Message render() {
    return SectionTreeEditorShape.newBuilder()
        .setRoot($.getRoot())
        .setSelection($.getSelection())
        .setMaxSectionNameLength(Section.MAX_NAME_LENGTH)
        .build();
  }

  @Override protected void handleEvent(Event e, Thing source) {
    if (e instanceof SectionTree.ChangeEvent) {
      List<EditableSectionNode> path = getPath(selectedEditor());
      List<ByteString> selectionCandidates = new ArrayList<>();
      selectionCandidates.add(selectedEditor().id());

      if (path.size() >= 2) {
        EditableSectionNode parentNode = path.get(path.size() - 2);
        int index = indexOfEditorId(selectedEditor().id(), parentNode);
        if (index < parentNode.getChildCount() - 1) {
          selectionCandidates.add(parentNode.getChild(index + 1).getEditor());
        }
        for (int i = path.size() - 2; i >= 0; --i) {
          selectionCandidates.add(path.get(i).getEditor());
        }
      }

      Set<ByteString> editorIds = syncEditors();

      ByteString selectId = null;
      for (ByteString id : selectionCandidates) {
        if (editorIds.contains(id)) {
          selectId = id;
          break;
        }
      }
      Validate.notNull(selectId);

      selection().setSelectedEditorId(selectId);
      post(SelectSectionEvent.INSTANCE);
    }
  }

  private static int indexOfEditorId(ByteString editorId, EditableSectionNode parent) {
    int n = parent.getChildCount();
    for (int i = 0; i < n; ++i) {
      if (parent.getChild(i).getEditor().equals(editorId)) {
        return i;
      }
    }
    throw new RuntimeException();
  }

  @Action(badInput = TestInputException.class, markAsModified = false)
  public void doSelect(SelectInput in) {
    ByteString sectionEditorId = Ids.fromAlienHexString(in.id);
    Check.input(containsEditorId(sectionEditorId));
    selection().setSelectedEditorId(sectionEditorId);
    post(SelectSectionEvent.INSTANCE);
  }

  private boolean containsEditorId(ByteString editorId) {
    return !getPath(editorId).isEmpty();
  }

  @Action(badInput = TestInputException.class)
  public void doAdd(AddInput in) {
    Section section = store().create(Section.class);
    section.setName(Check.inputNotNull(in.name));
    sectionTree().add(section, selectedSection());
  }

  private SectionEditor selectedEditor() {
    return selection().selectedEditor();
  }

  public Section selectedSection() {
    return selectedEditor().section();
  }

  @Action(badInput = TestInputException.class)
  public void doDelete() {
    sectionTree().remove(selectedSection());
  }

  private List<EditableSectionNode> getPath(SectionEditor editor) {
    return getPath(editor.id());
  }

  private List<EditableSectionNode> getPath(ByteString editorId) {
    List<EditableSectionNode> res = new ArrayList<>();
    res.add($.getRoot());
    getPathInternal(editorId, res);
    return res;
  }

  private boolean getPathInternal(ByteString editorId, List<EditableSectionNode> path) {
    EditableSectionNode last = path.get(path.size() - 1);
    if (last.getEditor().equals(editorId)) {
      return true;
    }

    for (EditableSectionNode node : last.getChildList()) {
      path.add(node);
      if (getPathInternal(editorId, path)) {
        return true;
      }
    }

    path.remove(path.size() - 1);
    return false;
  }

  @Action public void doPaste() {
    QuestionListEditor.open(selectedSection().questionList()).paste();
  }

  @Action(badInput = TestInputException.class)
  public void doMove(MoveInput in) {
    sectionTree().move(selectedSection(), in.forward);
  }

  @Override public List<Section> sectionPath() {
    return sectionTree().getPath(selectedSection());
  }
}
